package com.liaoyifan.core.opera;

import com.liaoyifan.core.annotation.NoWrapper;
import com.liaoyifan.core.constant.Constants;
import com.liaoyifan.core.constant.Properties;
import com.liaoyifan.core.model.Result;
import com.liaoyifan.core.properties.ResponseWrapperProperties;
import com.liaoyifan.core.valid.Lang;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@SuppressWarnings("NullableProblems")
@ControllerAdvice
@EnableConfigurationProperties(ResponseWrapperProperties.class)
@ConditionalOnProperty(name = Properties.RESPONSE_WRAPPER_ENABLE, havingValue = Constants.TRUE)
public class ResponseWrapper implements ResponseBodyAdvice<Object> {

    @Autowired private ResponseWrapperProperties properties;

    private static final List<Class<?>> DEFAULT_NO_WRAP_TYPES =
            Arrays.asList(
                    Result.class,
                    Resource.class,
                    InputStreamResource.class,
                    byte[].class,
                    ResponseEntity.class);

    private boolean isNoWrapperPath(String path) {
        if (Lang.isEmpty(properties.getNoWrapperPaths())) {
            return false;
        }
        return properties.getNoWrapperPaths().stream()
                .anyMatch(pattern -> Constants.PATH_MATCHER.match(pattern, path));
    }

    @Override
    public boolean supports(
            MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        if (returnType.hasMethodAnnotation(NoWrapper.class)
                || returnType.getDeclaringClass().isAnnotationPresent(NoWrapper.class)) {
            return false;
        }
        return DEFAULT_NO_WRAP_TYPES.stream()
                .noneMatch(type -> type.isAssignableFrom(returnType.getParameterType()));
    }

    @Override
    public Object beforeBodyWrite(
            Object body,
            MethodParameter returnType,
            MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType,
            ServerHttpRequest request,
            ServerHttpResponse response) {
        if (!selectedContentType.includes(MediaType.APPLICATION_JSON)) {
            return body;
        }
        String path = request.getURI().getPath();
        if (isNoWrapperPath(path)) {
            return body;
        }
        if (body == null && returnType.getParameterType() == void.class) {
            return Result.success();
        }
        return Result.success(body);
    }
}
